Repository navigation
Fix #15217 (gtk.cfg: g_idle_add and g_timeout_add take the data pointer) - #9008
Conversation
The data given to g_idle_add(), g_timeout_add() and their _seconds/_full variants is passed to the callback (and the destroy notify), which usually frees it. With <leak-ignore/> every such call was reported as Memory leak: data Configure them as <use> instead.
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
This PR causes a new false negative: leaks of a GMainContext passed to g_main_context_invoke / g_main_context_invoke_full are no longer reported. CheckLeakAutoVarImpl::functionCall applies a <use> function to every plain pointer variable in the call, because <use> has no argument number. So g_main_context_invoke(ctx, cb, NULL) now marks the context in ctx as taken over. That context comes from g_main_context_new(), which is in the g_main_context_unref group. GLib does not take ownership of the context; it only refs it internally. The base config reports Memory leak: ctx for this code; the PR does not. g_idle_add* and g_timeout_add* don't have this problem, because their only pointer parameters are the callback, the data and the destroy notify.
The memleak false positives that the PR is about are gone, and test/cfg/gtk.c passes. The new <arg> configuration for g_main_context_invoke* works: it reports a NULL callback as nullPointer and an uninitialized callback or priority as uninitvar.
Testing
Build: make -j8 cppcheck testrunner in pr/ succeeded.
cfg test: I ran test/cfg/gtk.c with the runtests.sh options (--check-library --platform=unix64 --enable=style,information --inconclusive --force --check-level=exhaustive --inline-suppr ... --library=gtk).
- With the PR's
gtk.cfg: clean, exit code 0. - With the base
gtk.cfg: 8memleakerrors in the new test functions. So the test does cover the change.
Regression found (I compared the PR's cfg with a copy of base's gtk.cfg, both run with the PR binary, since only the cfg changed):
static gboolean cb(gpointer data) { g_free(data); return FALSE; }
void ctx_leak(void)
{
GMainContext *ctx = g_main_context_new();
g_main_context_invoke(ctx, cb, NULL);
}
void ctx_ok(void)
{
GMainContext *ctx = g_main_context_new();
g_main_context_invoke(ctx, cb, NULL);
g_main_context_unref(ctx);
}$ cppcheck -q --library=/tmp/b/gtkbase.cfg t2.c
t2.c:16:1: error: Memory leak: ctx [memleak]
$ cppcheck -q --library=pr/cfg/gtk.cfg t2.c
(no output)
g_main_context_invoke_full(ctx, 0, cb, NULL, NULL) behaves the same way: base reports the leak, the PR does not.
Other checks:
g_main_context_invoke(NULL, NULL, NULL)now givesnullPointer, and an uninitialized callback or priority givesuninitvar.- A
malloc'd org_new'd pointer passed as the data is no longer reported, and no mismatch is reported either. - A conditional
if (x) g_idle_add(cb, p);is no longer reported. That matches howif (x) g_free(p);is already handled, so I don't count it as a problem of this PR.
Automated review of 51b97a0 by Claude. The pull request was built and tested locally.
| <use>g_register_data</use> | ||
| <use>g_idle_add</use> | ||
| <use>g_idle_add_full</use> | ||
| <use>g_main_context_invoke</use> |
There was a problem hiding this comment.
AI review
<use> has no argument number. CheckLeakAutoVarImpl::functionCall treats every plain pointer variable in a non-leak-ignore call as taken over, and that includes the GMainContext *context argument. As a result, this leak is no longer found (base reports Memory leak: ctx, the PR reports nothing):
void f(void) {
GMainContext *ctx = g_main_context_new();
g_main_context_invoke(ctx, cb, NULL);
}The same happens with g_main_context_invoke_full. GLib does not take ownership of the context. Consider adding a test/cfg/gtk.c case with // cppcheck-suppress memleak for this, and fixing it in the library or check, for example with a per-argument <use>/ownership setting. Otherwise, consider leaving the g_main_context_invoke* functions out of this change.
There was a problem hiding this comment.
Thanks, you're right: applies to every pointer argument, so g_main_context_invoke(ctx, ...) hid a leak of ctx. I dropped g_main_context_invoke and g_main_context_invoke_full from this PR; they keep for now.
51b97a0 to
46f1427
Compare
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
The earlier problem is fixed: the PR no longer touches g_main_context_invoke*, so a leaked GMainContext is reported again. The memleak false positives that the PR is about are gone, and test/cfg/gtk.c passes.
New finding (minor false negative): removing <leak-ignore/> also removes the use-after-free check for these calls. With base, g_free(p); g_idle_add(cb, p); gives deallocuse. With the PR it gives nothing. In CheckLeakAutoVarImpl::functionCall, only leak-ignore functions go through checkTokenInsideExpression; a <use> function only changes the pointer's alloc status. Every <use> function behaves this way, so this is a trade-off of the approach rather than a bug in the cfg. It could still be pinned down with a test in test/cfg/gtk.c. I found no other problems.
Earlier comments
| # | Comment | By | Where | Status |
|---|---|---|---|---|
| 1 | on g_main_context_invoke* hides leaks of the GMainContext argument | AI review | cfg/gtk.cfg:722 |
❔ Code removed |
Testing
Build: make -j8 cppcheck in pr/ succeeded. Only the cfg changed, so I compared the PR's cfg/gtk.cfg with a copy of base's gtk.cfg, both run with the PR binary.
cfg test: I ran test/cfg/gtk.c with the runtests.sh options (--check-library --inline-suppr --error-exitcode=1 ... --library=gtk). It was clean, exit code 0.
Scratch test (scratch/t.c):
#include <glib.h>
#include <stdlib.h>
static gboolean cb(gpointer d){g_free(d);return FALSE;}
void a(void){ gchar *p=g_strdup("x"); g_idle_add(cb,p); g_free(p); }
void b(void){ gchar *p=g_strdup("x"); g_free(p); g_idle_add(cb,p); }
void c(void){ char *p=malloc(3); g_idle_add(cb,p); free(p); }
void d(void){ gchar *p=g_strdup("x"); gchar *q=g_strdup("y"); g_timeout_add(1,cb,p); }
void e(int x){ gchar *p=g_strdup("x"); if(x) return; g_idle_add(cb,p); }
void f(void){ g_idle_add(cb,g_strdup("x")); }
void g(void){ GMainContext *ctx=g_main_context_new(); g_main_context_invoke(ctx,cb,NULL);}
void h(void){ gchar *p=g_strdup("x"); g_idle_add_full(0,cb,p,g_free); g_free(p);}$ cppcheck -q --library=gtkbase.cfg --template='{line}:{id}:{message}' t.c
5:deallocuse:Dereferencing 'p' after it is deallocated / released
7:memleak:Memory leak: p
7:memleak:Memory leak: q
8:memleak:Memory leak: p
10:memleak:Memory leak: ctx
9:leakNoVarFunctionCall:Allocation with g_strdup, g_idle_add doesn't release it.
$ cppcheck -q --library=pr/cfg/gtk.cfg --template='{line}:{id}:{message}' t.c
7:memleak:Memory leak: q
8:memleak:Memory leak: p
10:memleak:Memory leak: ctx
- Lines 7, 8 and 9: the PR removes the intended false positives. The real leaks (
q, and the early-return path) are still reported. - Line 10: the
GMainContextleak from the earlier review is reported again. - Line 5: the
deallocusewarning (use after free) is lost, which is the new false negative. - Lines 4 and 11: the double free through the callback is not reported, with base or with the PR.
Automated review of 46f1427 by Claude. The pull request was built and tested locally.
| <alloc init="true" no-fail="true">g_variant_type_dup_string</alloc> | ||
| <alloc init="true" no-fail="true">g_value_dup_string</alloc> | ||
| <use>g_register_data</use> | ||
| <use>g_idle_add</use> |
There was a problem hiding this comment.
AI review
Taking these functions off <leak-ignore/> also turns off the use-after-free check for their arguments. CheckLeakAutoVarImpl::functionCall calls checkTokenInsideExpression only for leak-ignore functions; a <use> function only gets changeAllocStatus. As a result:
void b(void){ gchar *p=g_strdup("x"); g_free(p); g_idle_add(cb,p); }Base reports deallocuse: Dereferencing 'p' after it is deallocated / released. With this PR nothing is reported. This is how <use> works in general, so it may be acceptable, but it is a new false negative for these six functions.
direction="in" on the gpointer data argument made cppcheck suggest a pointer to const (constVariablePointer) for the data, although the function hands it to the callback and takes a non-const gpointer. g_idle_add_full already had no direction on it.
A <use> function does not get the deallocuse check that a <leak-ignore/> function gets, so passing freed data to g_idle_add() is no longer reported. Record it as a TODO suppression.
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
I found no new problems. The two issues raised earlier are dealt with:
- The false negative for a
GMainContextleak throughg_main_context_invoke*is fixed, because those functions were dropped from the PR. - The lost
deallocusecheck for these six functions is still there. It is a limitation of<use>itself, and the PR now records it as a TODO suppression intest/cfg/gtk.c.
The memleak false positives the PR targets are gone. Real leaks of other variables are still reported.
Earlier comments
| # | Comment | By | Where | Status |
|---|---|---|---|---|
| 1 | on g_main_context_invoke* hides leaks of the GMainContext argument | AI review | cfg/gtk.cfg:722 |
✅ Fixed |
| 2 | Removing loses the deallocuse check for g_idle_add/g_timeout_add data | AI review | cfg/gtk.cfg:720 |
💬 Answered: Accepted as a known limitation of , recorded as a TODO suppression in test/cfg/gtk.c |
Testing
Build: make -j8 cppcheck in pr/ succeeded.
cfg test: I ran test/cfg/gtk.c with the PR binary and --library=gtk, using the runtests.sh style options including --debug-warnings and --inline-suppr. The only messages were unmatched suppressions for the extra --suppress options that I had passed myself. The test file gave no warnings.
Scratch comparison: I ran the PR binary on the same file twice, once with base's gtk.cfg and once with the PR's gtk.cfg:
static gboolean cb(gpointer d){return FALSE;}
void a(void){ GHashTable *h = g_hash_table_new(NULL,NULL); g_idle_add(cb,h); }
void b(void){ GObject *o = g_object_new(0,NULL); g_timeout_add(1,cb,o); }
void c(void){ GList *l = g_list_alloc(); g_idle_add(cb,l); }
void d(void){ GString *s = g_string_new("x"); g_idle_add(cb,s); }
void e(void){ gchar *p = g_strdup("x"); g_idle_add(cb,p); g_free(p); }
void f(void){ gchar *p = g_strdup("x"); g_idle_add(cb,p); p[0]=0; }
void g(void){ gchar *p = g_strdup("x"); guint id = g_idle_add(cb,p); (void)id; }
void h(void){ GHashTable *h = g_hash_table_new(NULL,NULL); g_idle_add(cb,h); g_hash_table_unref(h);}- Base cfg:
memleakin a, b, c, d, f and g, plusconstVariablePointerfor the data variables. - PR cfg: none of these. Only the unrelated
constParameterCallbackremains.
Data from other allocator groups (hash table, GObject, GList, GString) is now treated as handed over. I saw no new mismatch or double-free false positives.
Automated review of c10a655 by Claude. The pull request was built and tested locally.
I don't have a Trac account, so there is no ticket for this; the details are below. Feel free to create one and I'll add it to the title.
GLib passes the data to the callback, and to the destroy notify of the
_fullvariants, which commonly frees it, so there is no leak. gtk.cfg marked these functions<leak-ignore/>:g_idle_add,g_idle_add_fullg_timeout_add,g_timeout_add_seconds,g_timeout_add_full,g_timeout_add_seconds_fullThey are now
<use>in theg_freememory group, without<leak-ignore/>. Their only pointer arguments are the callback, the data and the destroy notify, so<use>can't hide another leak. Their data argument also losesdirection="in", which made cppcheck suggest a pointer to const for the data.This is the gtk.cfg change that #8953 (#15137) was made for: without that fix,
--check-librarystill reportedcheckLibraryUseIgnorefor these functions. Here<use>means what the reference describes: the function takes over the pointer.Known limitation: a
<use>function doesn't get the deallocuse check, sog_free(p); g_idle_add(cb, p);is no longer reported.test/cfg/gtk.crecords this as a TODO suppression.Tests:
test/cfg/gtk.ccovers all six functions; the memleak false positives show without the change.cfg/gtk.cfgvalidates againstcppcheck-cfg.rng.